/*
 * Copyright (c) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "permission_fuzzer.h"
#include <unistd.h>
#include <thread>
#include <chrono>
#include "accesstoken_kit.h"
#include "token_setproc.h"
#include "nativetoken_kit.h"
#include "securec.h"
#include "sensor_agent.h"
#include "sensor_agent_type.h"
#include "sensor_errors.h"

#undef LOG_TAG
#define LOG_TAG "PermissionFuzzTest"

namespace OHOS {
namespace Sensors {
using namespace OHOS::HiviewDFX;
using namespace OHOS::Security::AccessToken;
using OHOS::Security::AccessToken::AccessTokenID;
using namespace PermissionFuzzerConstants;

template<class T>
size_t GetObject(T &object, const uint8_t *data, size_t size)
{
    size_t objectSize = sizeof(object);
    if (objectSize > size) {
        return 0;
    }
    errno_t ret = memcpy_s(&object, objectSize, data, objectSize);
    if (ret != EOK) {
        return 0;
    }
    return objectSize;
}

void SetupNoPermission()
{
    const char **perms = new (std::nothrow) const char *[SINGLE_PERMISSION_COUNT];
    CHKPV(perms);
    perms[FIRST_PERMISSION_INDEX] = "ohos.permission.DISTRIBUTED_DATASYNC";
    TokenInfoParams infoInstance = {
        .dcapsNum = NO_DCAPS,
        .permsNum = SINGLE_PERMISSION_COUNT,
        .aclsNum = NO_ACLS,
        .dcaps = nullptr,
        .perms = perms,
        .acls = nullptr,
        .processName = "PermissionFuzzTest_NoPerms",
        .aplStr = "normal",
    };
    uint64_t tokenId = GetAccessTokenId(&infoInstance);
    SetSelfTokenID(tokenId);
    AccessTokenKit::ReloadNativeTokenInfo();
    delete[] perms;
}

void SetupPartialPermission()
{
    const char **perms = new (std::nothrow) const char *[SINGLE_PERMISSION_COUNT];
    CHKPV(perms);
    perms[FIRST_PERMISSION_INDEX] = "ohos.permission.ACCELEROMETER";
    TokenInfoParams infoInstance = {
        .dcapsNum = NO_DCAPS,
        .permsNum = SINGLE_PERMISSION_COUNT,
        .aclsNum = NO_ACLS,
        .dcaps = nullptr,
        .perms = perms,
        .acls = nullptr,
        .processName = "PermissionFuzzTest_Partial",
        .aplStr = "normal",
    };
    uint64_t tokenId = GetAccessTokenId(&infoInstance);
    SetSelfTokenID(tokenId);
    AccessTokenKit::ReloadNativeTokenInfo();
    delete[] perms;
}

void SetupFullPermission()
{
    const char **perms = new (std::nothrow) const char *[DUAL_PERMISSION_COUNT];
    CHKPV(perms);
    perms[FIRST_PERMISSION_INDEX] = "ohos.permission.ACCELEROMETER";
    perms[SECOND_PERMISSION_INDEX] = "ohos.permission.MANAGE_SENSOR";
    TokenInfoParams infoInstance = {
        .dcapsNum = NO_DCAPS,
        .permsNum = DUAL_PERMISSION_COUNT,
        .aclsNum = NO_ACLS,
        .dcaps = nullptr,
        .perms = perms,
        .acls = nullptr,
        .processName = "PermissionFuzzTest_Full",
        .aplStr = "system_core",
    };
    uint64_t tokenId = GetAccessTokenId(&infoInstance);
    SetSelfTokenID(tokenId);
    AccessTokenKit::ReloadNativeTokenInfo();
    delete[] perms;
}

void SensorDataCallbackImpl(SensorEvent *event)
{
    if (event == nullptr) {
        return;
    }
}

static void TestNoPermissionScenario(SensorUser* user, int32_t sensorId)
{
    SetupNoPermission();
    
    SensorInfo *sensorList = nullptr;
    int32_t count = INITIAL_COUNT;
    GetAllSensors(&sensorList, &count);
    
    int32_t ret = SubscribeSensor(sensorId, user);
    if (ret == SUCCESS_CODE) {
        ActivateSensor(sensorId, user);
        std::this_thread::sleep_for(std::chrono::milliseconds(DEFAULT_SLEEP_MS));
        DeactivateSensor(sensorId, user);
        UnsubscribeSensor(sensorId, user);
    }
}

static void TestPartialPermissionScenario(SensorUser* user)
{
    SetupPartialPermission();
    
    int32_t ret1 = SubscribeSensor(SENSOR_TYPE_ID_ACCELEROMETER, user);
    if (ret1 == SUCCESS_CODE) {
        ActivateSensor(SENSOR_TYPE_ID_ACCELEROMETER, user);
        std::this_thread::sleep_for(std::chrono::milliseconds(DEFAULT_SLEEP_MS));
        DeactivateSensor(SENSOR_TYPE_ID_ACCELEROMETER, user);
        UnsubscribeSensor(SENSOR_TYPE_ID_ACCELEROMETER, user);
    }
    
    int32_t ret2 = SubscribeSensor(SENSOR_TYPE_ID_GYROSCOPE, user);
    if (ret2 == SUCCESS_CODE) {
        DeactivateSensor(SENSOR_TYPE_ID_GYROSCOPE, user);
        UnsubscribeSensor(SENSOR_TYPE_ID_GYROSCOPE, user);
    }
}

static void TestPermissionUpgradeScenario(SensorUser* user, int32_t sensorId)
{
    SetupNoPermission();
    (void)SubscribeSensor(sensorId, user);
    
    SetupFullPermission();
    int32_t ret2 = SubscribeSensor(sensorId, user);
    if (ret2 == SUCCESS_CODE) {
        ActivateSensor(sensorId, user);
        std::this_thread::sleep_for(std::chrono::milliseconds(DEFAULT_SLEEP_MS));
        DeactivateSensor(sensorId, user);
        UnsubscribeSensor(sensorId, user);
    }
}

static void TestPermissionDowngradeScenario(SensorUser* user, int32_t sensorId)
{
    SetupFullPermission();
    int32_t ret = SubscribeSensor(sensorId, user);
    if (ret == SUCCESS_CODE) {
        ActivateSensor(sensorId, user);
        SetupNoPermission();
        std::this_thread::sleep_for(std::chrono::milliseconds(DEFAULT_SLEEP_MS));
        DeactivateSensor(sensorId, user);
        UnsubscribeSensor(sensorId, user);
    }
}

static void TestAdminApiScenario()
{
    SetupNoPermission();
    
    int32_t pid = getpid();
    SuspendSensors(pid);
    ResumeSensors(pid);
    ResetSensors();
    
    SensorActiveInfo *activeInfos = nullptr;
    int32_t count = INITIAL_COUNT;
    GetActiveSensorInfos(pid, &activeInfos, &count);
}

static void TestEnhancedApiScenario(SensorUser* user, int32_t sensorId)
{
    SetupPartialPermission();
    
    SensorIdentifier identifier;
    identifier.deviceId = DEFAULT_DEVICE_ID;
    identifier.sensorType = SENSOR_TYPE_ID_ACCELEROMETER;
    identifier.sensorId = sensorId;
    identifier.location = DEFAULT_LOCATION;
    
    int32_t ret = SubscribeSensorEnhanced(identifier, user);
    if (ret == SUCCESS_CODE) {
        ActivateSensorEnhanced(identifier, user);
        std::this_thread::sleep_for(std::chrono::milliseconds(DEFAULT_SLEEP_MS));
        DeactivateSensorEnhanced(identifier, user);
        UnsubscribeSensorEnhanced(identifier, user);
    }
}

static void TestMixedPermissionScenario(SensorUser* user, int32_t sensorId)
{
    for (int i = 0; i < LOOP_COUNT; i++) {
        if (i % MODULO_DIVISOR == 0) {
            SetupFullPermission();
        } else {
            SetupNoPermission();
        }
        
        int32_t ret = SubscribeSensor(sensorId, user);
        if (ret == SUCCESS_CODE) {
            ActivateSensor(sensorId, user);
            std::this_thread::sleep_for(std::chrono::milliseconds(MIXED_SLEEP_MS));
            DeactivateSensor(sensorId, user);
            UnsubscribeSensor(sensorId, user);
        }
    }
}

void PermissionFuzzTest(const uint8_t *data, size_t size)
{
    if (data == nullptr || size < DATA_MIN_SIZE) {
        return;
    }
    
    uint8_t scenario = data[DATA_SCENARIO_INDEX] % SCENARIO_COUNT;
    
    SensorUser user;
    user.callback = SensorDataCallbackImpl;
    
    int32_t sensorId = SENSOR_TYPE_ID_ACCELEROMETER;
    
    switch (scenario) {
        case SCENARIO_NO_PERMISSION:
            TestNoPermissionScenario(&user, sensorId);
            break;
        case SCENARIO_PARTIAL_PERMISSION:
            TestPartialPermissionScenario(&user);
            break;
        case SCENARIO_PERMISSION_UPGRADE:
            TestPermissionUpgradeScenario(&user, sensorId);
            break;
        case SCENARIO_PERMISSION_DOWNGRADE:
            TestPermissionDowngradeScenario(&user, sensorId);
            break;
        case SCENARIO_ADMIN_API:
            TestAdminApiScenario();
            break;
        case SCENARIO_ENHANCED_API:
            TestEnhancedApiScenario(&user, sensorId);
            break;
        default:
            TestMixedPermissionScenario(&user, sensorId);
            break;
    }
}

} // namespace Sensors
} // namespace OHOS

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
    OHOS::Sensors::PermissionFuzzTest(data, size);
    return 0;
}
